/**
 * Copyright (c) 2006-2016, JGraph Ltd
 * Copyright (c) 2006-2016, Gaudenz Alder
 */
/**
 * Class: mxRubberband
 * 
 * 拖拽圈选功能
 * Event handler that selects rectangular regions. This is not built-into
 * <mxGraph>. To enable rubberband selection in a graph, use the following code.
 * 
 * Example:
 * 
 * (code)
 * var rubberband = new mxRubberband(graph);
 * (end)
 * 
 * Constructor: mxRubberband
 * 
 * Constructs an event handler that selects rectangular regions in the graph
 * using rubberband selection.
 */
function mxRubberband(graph) {
  if (graph != null) {
    this.graph = graph;
    this.graph.addMouseListener(this);

    // Handles force rubberband event
    this.forceRubberbandHandler = mxUtils.bind(this, function (sender, evt) {
      const evtName = evt.getProperty('eventName');
      const me = evt.getProperty('event');

      if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me)) {
        const offset = mxUtils.getOffset(this.graph.container);
        const origin = mxUtils.getScrollOrigin(this.graph.container);
        origin.x -= offset.x;
        origin.y -= offset.y;
        this.start(me.getX() + origin.x, me.getY() + origin.y);
        me.consume(false);
      }
    });
		
    this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);
		
    // Repaints the marquee after autoscroll
    this.panHandler = mxUtils.bind(this, function () {
      this.repaint();
    });
		
    this.graph.addListener(mxEvent.PAN, this.panHandler);
		
    // Does not show menu if any touch gestures take place after the trigger
    this.gestureHandler = mxUtils.bind(this, function (sender, eo) {
      if (this.first != null) {
        this.reset();
      }
    });
		
    this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
		
    // Automatic deallocation of memory
    if (mxClient.IS_IE) {
      mxEvent.addListener(window, 'unload',
        mxUtils.bind(this, function () {
          this.destroy();
        }));
    }
  }
}

/**
 * Variable: defaultOpacity
 * 
 * Specifies the default opacity to be used for the rubberband div. Default
 * is 20.
 */
mxRubberband.prototype.defaultOpacity = 20;

/**
 * Variable: enabled
 * 
 * Specifies if events are handled. Default is true.
 */
mxRubberband.prototype.enabled = true;

/**
 * Variable: div
 * 
 * Holds the DIV element which is currently visible.
 */
mxRubberband.prototype.div = null;

/**
 * Variable: sharedDiv
 * 
 * Holds the DIV element which is used to display the rubberband.
 */
mxRubberband.prototype.sharedDiv = null;

/**
 * Variable: currentX
 * 
 * Holds the value of the x argument in the last call to <update>.
 */
mxRubberband.prototype.currentX = 0;

/**
 * Variable: currentY
 * 
 * Holds the value of the y argument in the last call to <update>.
 */
mxRubberband.prototype.currentY = 0;

/**
 * Variable: fadeOut
 * 
 * Optional fade out effect. Default is false.
 */
mxRubberband.prototype.fadeOut = false;

/**
 * Function: isEnabled
 * 
 * Returns true if events are handled. This implementation returns
 * <enabled>.
 */
mxRubberband.prototype.isEnabled = function () {
  return this.enabled;
};
		
/**
 * Function: setEnabled
 * 
 * Enables or disables event handling. This implementation updates
 * <enabled>.
 */
mxRubberband.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: isForceRubberbandEvent
 * 
 * Returns true if the given <mxMouseEvent> should start rubberband selection.
 * This implementation returns true if the alt key is pressed.
 */
mxRubberband.prototype.isForceRubberbandEvent = function (me) {
  return mxEvent.isAltDown(me.getEvent());
};

/**
 * Function: mouseDown
 * 
 * Handles the event by initiating a rubberband selection. By consuming the
 * event all subsequent events of the gesture are redirected to this
 * handler.
 */
mxRubberband.prototype.mouseDown = function (sender, me) {
  if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled()
		&& me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent())) {
    const offset = mxUtils.getOffset(this.graph.container);
    const origin = mxUtils.getScrollOrigin(this.graph.container);
    origin.x -= offset.x;
    origin.y -= offset.y;
    this.start(me.getX() + origin.x, me.getY() + origin.y);

    // Does not prevent the default for this event so that the
    // event processing chain is still executed even if we start
    // rubberbanding. This is required eg. in ExtJs to hide the
    // current context menu. In mouseMove we'll make sure we're
    // not selecting anything while we're rubberbanding.
    me.consume(false);
  }
};

/**
 * Function: start
 * 
 * Sets the start point for the rubberband selection.
 */
mxRubberband.prototype.start = function (x, y) {
  this.first = new mxPoint(x, y);

  const container = this.graph.container;
	
  function createMouseEvent(evt) {
    const me = new mxMouseEvent(evt);
    const pt = mxUtils.convertPoint(container, me.getX(), me.getY());
		
    me.graphX = pt.x;
    me.graphY = pt.y;
		
    return me;
  }

  this.dragHandler = mxUtils.bind(this, function (evt) {
    this.mouseMove(this.graph, createMouseEvent(evt));
  });

  this.dropHandler = mxUtils.bind(this, function (evt) {
    this.mouseUp(this.graph, createMouseEvent(evt));
  });

  // Workaround for rubberband stopping if the mouse leaves the container in Firefox
  if (mxClient.IS_FF) {
    mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);
  }
};

/**
 * Function: mouseMove
 * 
 * Handles the event by updating therubberband selection.
 */
mxRubberband.prototype.mouseMove = function (sender, me) {
  if (!me.isConsumed() && this.first != null) {
    const origin = mxUtils.getScrollOrigin(this.graph.container);
    const offset = mxUtils.getOffset(this.graph.container);
    origin.x -= offset.x;
    origin.y -= offset.y;
    const x = me.getX() + origin.x;
    const y = me.getY() + origin.y;
    const dx = this.first.x - x;
    const dy = this.first.y - y;
    const tol = this.graph.tolerance;
		
    if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) {
      if (this.div == null) {
        this.div = this.createShape();
      }
			
      // Clears selection while rubberbanding. This is required because
      // the event is not consumed in mouseDown.
      mxUtils.clearSelection();
			
      this.update(x, y);
      me.consume();
    }
  }
};

/**
 * Function: createShape
 * 
 * Creates the rubberband selection shape.
 */
mxRubberband.prototype.createShape = function () {
  if (this.sharedDiv == null) {
    this.sharedDiv = document.createElement('div');
    this.sharedDiv.className = 'mxRubberband';
    mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
  }

  this.graph.container.appendChild(this.sharedDiv);
  const result = this.sharedDiv;
	
  if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) {
    this.sharedDiv = null;
  }
		
  return result;
};

/**
 * Function: isActive
 * 
 * Returns true if this handler is active.
 */
mxRubberband.prototype.isActive = function (sender, me) {
  return this.div != null && this.div.style.display != 'none';
};

/**
 * Function: mouseUp
 * 
 * Handles the event by selecting the region of the rubberband using
 * <mxGraph.selectRegion>.
 */
mxRubberband.prototype.mouseUp = function (sender, me) {
  const active = this.isActive();
  this.reset();
	
  if (active) {
    this.execute(me.getEvent());
    me.consume();
  }
};

/**
 * Function: execute
 * 
 * Resets the state of this handler and selects the current region
 * for the given event.
 */
mxRubberband.prototype.execute = function (evt) {
  const rect = new mxRectangle(this.x, this.y, this.width, this.height);
  this.graph.selectRegion(rect, evt);
};

/**
 * Function: reset
 * 
 * Resets the state of the rubberband selection.
 */
mxRubberband.prototype.reset = function () {
  if (this.div != null) {
    if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) {
      const temp = this.div;
      mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear');
      temp.style.pointerEvents = 'none';
      temp.style.opacity = 0;
		    
		    window.setTimeout(() => {
		    		temp.parentNode.removeChild(temp);
		    	}, 200);	
    } else {
      this.div.parentNode.removeChild(this.div);
    }
  }

  mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);
  this.dragHandler = null;
  this.dropHandler = null;
	
  this.currentX = 0;
  this.currentY = 0;
  this.first = null;
  this.div = null;
};

/**
 * Function: update
 * 
 * Sets <currentX> and <currentY> and calls <repaint>.
 */
mxRubberband.prototype.update = function (x, y) {
  this.currentX = x;
  this.currentY = y;
	
  this.repaint();
};

/**
 * Function: repaint
 * 
 * Computes the bounding box and updates the style of the <div>.
 */
mxRubberband.prototype.repaint = function () {
  if (this.div != null) {
    const x = this.currentX - this.graph.panDx;
    const y = this.currentY - this.graph.panDy;
		
    this.x = Math.min(this.first.x, x);
    this.y = Math.min(this.first.y, y);
    this.width = Math.max(this.first.x, x) - this.x;
    this.height = Math.max(this.first.y, y) - this.y;

    const dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
    const dy = (mxClient.IS_VML) ? this.graph.panDy : 0;
		
    this.div.style.left = `${this.x + dx}px`;
    this.div.style.top = `${this.y + dy}px`;
    this.div.style.width = `${Math.max(1, this.width)}px`;
    this.div.style.height = `${Math.max(1, this.height)}px`;
  }
};

/**
 * Function: destroy
 * 
 * Destroys the handler and all its resources and DOM nodes. This does
 * normally not need to be called, it is called automatically when the
 * window unloads.
 */
mxRubberband.prototype.destroy = function () {
  if (!this.destroyed) {
    this.destroyed = true;
    this.graph.removeMouseListener(this);
    this.graph.removeListener(this.forceRubberbandHandler);
    this.graph.removeListener(this.panHandler);
    this.reset();
		
    if (this.sharedDiv != null) {
      this.sharedDiv = null;
    }
  }
};

window.mxRubberband = mxRubberband;
